package service

import (
	"RpcNasSync/logger"
	"bytes"
	"context"
	"errors"
	"fmt"
	"github.com/codeskyblue/go-sh"
	"github.com/smallnest/rpcx/server"
	"github.com/smallnest/rpcx/share"
	"go.uber.org/zap"
	"io"
	"net"
	"os"
	"os/exec"
	"strconv"
)

type Args struct {
	Command string
}

type Reply struct {
	Stdout string
	Stderr string
	Exit   int
}

type ServiceShell struct {
	Command string
}

func (s *ServiceShell) ShellCommand(ctx context.Context, args *Args, reply *Reply) (err error) {
	defer func() {
		if r := recover(); r != nil {
			// 记录 panic 错误，并输出堆栈信息
			logger.LogError("panic in ShellCommand", zap.Any("error", r), zap.Stack("stack"))
		}
	}()
	cmdString := args.Command
	clientAddr := ctx.Value(server.RemoteConnContextKey).(net.Conn).RemoteAddr().String()
	logger.LogInfo("execute command", zap.String("clientAddr", clientAddr), zap.String("command", cmdString))
	session := sh.NewSession()
	// 类 Unix 系统（Linux/macOS）使用 bash 或 sh
	cmd := session.Command("bash", "-c", cmdString)
	var stdoutBuf, stderrBuf bytes.Buffer
	cmd.Stdout = &stdoutBuf
	cmd.Stderr = &stderrBuf
	err = cmd.Run()
	reply.Stdout = stdoutBuf.String()
	reply.Stderr = stderrBuf.String()
	if err != nil {
		logger.LogError("error executing command:", zap.String("command", cmdString), zap.String("stdout", reply.Stdout), zap.String("stderr", reply.Stderr), zap.Error(err))
		var exitErr *exec.ExitError
		if errors.As(err, &exitErr) {
			reply.Exit = exitErr.ExitCode()
		}
		return nil
	}
	logger.LogInfo("execute command success", zap.String("clientAddr", clientAddr), zap.String("command", cmdString), zap.String("stdout", reply.Stdout), zap.String("stderr", reply.Stderr))
	return nil
}

func SaveFileHandler(con net.Conn, args *share.FileTransferArgs) {
	defer func() {
		if r := recover(); r != nil {
			// 记录 panic 错误，并输出堆栈信息
			logger.LogError("panic in saveFileHandler", zap.Any("error", r), zap.Stack("stack"))
		}
	}()
	defer con.Close()
	clientAddr := con.RemoteAddr().String()
	// 文件大小为-1,则表示网络传输到标准输入流中
	if args.FileSize == -1 {
		logger.LogInfo("receive file from stdin-stream", zap.String("clientAddr", clientAddr))
		// 传递args.FileName为命令
		session := sh.NewSession()
		cmd1 := session.Command("bash", "-c", args.FileName)
		cmd1.Stdin = con
		// 将标准输出和标准错误重定向到字符stdout中
		oldout := cmd1.Stdout
		olderr := cmd1.Stderr
		defer func() {
			cmd1.Stdout = oldout
			cmd1.Stderr = olderr
		}()
		stdout := bytes.NewBuffer(nil)
		cmd1.Stdout = stdout
		cmd1.Stderr = stdout

		if err := cmd1.Start(); err != nil {
			logger.LogError("start command failed", zap.String("clientAddr", clientAddr), zap.String("error", stdout.String()))
			con.Write(stdout.Bytes())
			return
		}

		if err := cmd1.Wait(); err != nil {
			logger.LogError("command wait failed", zap.String("clientAddr", clientAddr), zap.String("error", stdout.String()))
			con.Write(stdout.Bytes())
			return
		}
		logger.LogInfo("receive file success", zap.String("clientAddr", clientAddr))
	} else {
		fileName := args.Meta["fileName"]
		fileMode, err := strconv.ParseInt(args.Meta["fileMode"], 8, 32)
		if err != nil {
			logger.LogError("receive fileMode failed", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Error(err))
			con.Write([]byte(err.Error()))
			return
		}
		logger.LogInfo("receive file", zap.String("clientAddr", clientAddr), zap.String("name", fileName), zap.Int64("fileSize/byte", args.FileSize), zap.String("fileMode", os.FileMode(fileMode).String()))
		dstFile, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(fileMode))
		if err != nil {
			logger.LogError("create dstFile failed", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Error(err))
			defer os.Remove(fileName)
			con.Write([]byte(err.Error()))
			return
		}
		err = os.Chmod(fileName, os.FileMode(fileMode))
		if err != nil {
			logger.LogError("change dstFile mode failed", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Error(err))
			defer os.Remove(fileName)
			con.Write([]byte(err.Error()))
			return
		}
		// 写入ok表示传输前置工作已做好
		con.Write([]byte("ok"))
		defer dstFile.Close()
		n, err := io.Copy(dstFile, con)
		if err != nil {
			logger.LogError("receive file failed", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Error(err))
			defer os.Remove(fileName)
			con.Write([]byte(err.Error()))
			return
		}
		if n < args.FileSize {
			logger.LogError("receive file interrupt", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Int64("receive file size/byte", n))
			defer os.Remove(fileName)
			con.Write([]byte(err.Error()))
			return
		}
		logger.LogInfo("receive file success", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName))
	}
}

func DownloadFileHandler(conn net.Conn, args *share.DownloadFileArgs) {
	defer func() {
		if r := recover(); r != nil {
			// 记录 panic 错误，并输出堆栈信息
			logger.LogError("panic in downloadFileHandler", zap.Any("error", r), zap.Stack("stack"))
		}
	}()
	fileName := args.FileName
	clientAddr := conn.RemoteAddr().String()
	logger.LogInfo("send file", zap.String("clientAddr", clientAddr), zap.String("name", args.FileName), zap.Any("meta", args.Meta))
	// 打开文件
	file, err := os.Open(fileName)

	defer conn.Close()
	defer file.Close()
	if err != nil {
		logger.LogError("failed to open file", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Error(err))
		conn.Write([]byte(fmt.Sprintf("Error from server: %v", err)))
		return
	}
	fi, err := file.Stat()
	if err != nil {
		logger.LogError("failed to stat file properties", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Error(err))
		conn.Write([]byte(fmt.Sprintf("Error from server: %v", err)))
		return
	}
	if fi.IsDir() {
		logger.LogError("file is a directory", zap.String("clientAddr", clientAddr), zap.String("fileName", fileName), zap.Error(err))
		conn.Write([]byte(fmt.Sprintf("Error from server: %s is a directory", fileName)))
		return
	}
	conn.Write([]byte(fmt.Sprintf("%o", fi.Mode().Perm())))
	// 传输文件
	n, err := io.Copy(conn, file)
	if err != nil {
		logger.LogError("send file failed", zap.String("clientAddr", clientAddr), zap.Error(err), zap.String("file", fileName), zap.Int64("send file size/byte", n))
		conn.Write([]byte(fmt.Sprintf("Error from server: %v", err)))
		return
	}
	logger.LogInfo("send file success", zap.String("clientAddr", clientAddr), zap.String("clientAddr", clientAddr))
}
